示例 PRO

import { Button, List, Navigation, NavigationStack, Path, Script, Text, useState } from "scripting"

const folder = Path.join(FileManager.temporaryDirectory, `audio-pcm-${Date.now()}-${Math.random().toString(16).slice(2)}`)
let created = false
let pending: Promise<void> | null = null
let alive = true
function Demo() {
  const dismiss = Navigation.useDismiss()
  const [message, setMessage] = useState("Ready: no microphone or audio output")
  const [busy, setBusy] = useState(false)
  const report = (text: string) => { if (alive) setMessage(text) }
  async function run(withFile: boolean) {
    if (pending) return
    setBusy(true)
    pending = (async () => {
      // Complete asynchronous authorization before synchronous value constructors.
      await AVAudioEngine.capabilities()
      const format = new AVAudioFormat({ sampleRate: 48000, channelCount: 2, sampleType: "float32", interleaved: false })
      let buffer: AVAudioPCMBuffer | null = null, copied: AVAudioPCMBuffer | null = null
      let converted: AVAudioPCMBuffer | AVAudioCompressedBuffer | null = null, read: AVAudioPCMBuffer | null = null
      let converter: AVAudioConverter | null = null, writer: AVAudioFile | null = null, reader: AVAudioFile | null = null
      try {
        const left = new Float32Array([0.1, 0.2, 0.3, 0.4]), right = new Float32Array([-0.1, -0.2, -0.3, -0.4])
        buffer = await AVAudioPCMBuffer.fromChannels([left, right], format)
        left.fill(0); right.fill(0); copied = await buffer.copy(); await buffer.dispose(); buffer = null
        const packed = await copied.copyInterleaved(), channels = await copied.copyChannels()
        const int16 = new AVAudioFormat({ sampleRate: 48000, channelCount: 2, sampleType: "int16", interleaved: true })
        converter = await AVAudioConverter.create(format, int16, { primeMethod: "none" })
        const result = await converter.convert([{ buffer: copied, endOfStream: true }], 256); converted = result.buffer
        if (!(converted instanceof AVAudioPCMBuffer)) throw Error("Expected PCM conversion output")
        const integers = await converted.copyInterleaved()
        let fileMessage = ""
        if (withFile) {
          if (!created) { await FileManager.createDirectory(folder, true); created = true }
          const path = Path.join(folder, `roundtrip-${Date.now()}.caf`)
          writer = await AVAudioFile.openForWriting(path, format.settings); await writer.write(copied); await writer.close(); writer = null
          reader = await AVAudioFile.openForReading(path); read = await reader.read(4)
          const restored = await read.copyInterleaved()
          if (read.frameLength !== 4 || restored.some((value, index) => Math.abs(value - packed[index]) > 0.000001)) throw Error("File PCM round trip mismatch")
          await read.dispose(); read = null; await reader.seek(4); read = await reader.read(4)
          if (read.frameLength !== 0) throw Error("Expected zero frames at EOF")
          const state = await reader.getState(); fileMessage = `\nCAF round trip: ${state.length} frames, EOF ${read.frameLength}\n${path}`
        }
        report(`Independent copy: ${copied.frameLength} frames, ${copied.format.channelCount} channels\nPacked: ${Array.from(packed).map(v => v.toFixed(1)).join(", ")}\nLeft: ${Array.from(channels[0]).map(v => v.toFixed(1)).join(", ")}\nConverted Int16: ${Array.from(integers).join(", ")}${fileMessage}`)
      } finally {
        await writer?.close().catch(error => { const failure = error as AVAudio.Error; report(`${String(error)}${failure.partialFilePath ? `\nRecovery: ${failure.partialFilePath}` : ""}`) })
        await reader?.close(); await converter?.dispose(); await converted?.dispose(); await read?.dispose(); await buffer?.dispose(); await copied?.dispose()
      }
    })().catch(error => report(String(error))).finally(() => { pending = null; if (alive) setBusy(false) })
    await pending
  }
  return <NavigationStack><List navigationTitle="PCM, Files and Conversion" toolbar={{ cancellationAction: <Button title="Close" action={dismiss} /> }}>
    <Text>{message}</Text>
    <Button title="Copy PCM and convert Float32 to Int16" action={() => { void run(false) }} disabled={busy} />
    <Button title="Also write and read a temporary CAF" action={() => { void run(true) }} disabled={busy} />
  </List></NavigationStack>
}
async function main() { try { await Navigation.present({ element: <Demo /> }) } finally { try { alive = false; await pending; if (created) await FileManager.remove(folder); } finally { Script.exit() } } }
void main()